#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>

#include <boost/operators.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/filesystem/fstream.hpp>
namespace fs = boost::filesystem;

#include <vw/Core/Debugging.h>
#include <vw/Image/BlockRasterize.h>
#include <vw/FileIO/DiskImageView.h>
#include <vw/Mosaic/QuadTreeGenerator.h>
#include <vw/Mosaic/GMapQuadTreeConfig.h>
#include <vw/Mosaic/ImageComposite.h>
using namespace vw;

#include <opencv/highgui.h>
#include "inputimage.h"

using namespace cshotel;

//Functor generating the names as required for DeepZoom image pyramid
std::string dz_formatted_image_path::operator()( mosaic::QuadTreeGenerator const& qtree, std::string const& name, int32 levels_per_directory) {
    fs::path path( qtree.get_name(), fs::native );

    Vector2i pos(0,0);
    for ( int i=0; i<(int)name.length(); ++i ) {
        pos *= 2;
        if( name[i]=='1' ) pos += Vector2i(1,0);
        else if( name[i]=='2' ) pos += Vector2i(0,1);
        else if( name[i]=='3' ) pos += Vector2i(1,1);
        else if( name[i]!='0' ) {
            vw_throw( LogicErr() << "DeepZoom output format incompatible with non-standard quadtree structure" );
        }
    }
    std::ostringstream oss;
    oss << qtree.get_name() << "_files/" << name.length() << "/" << pos.x() << "_" << pos.y();
    path /= oss.str();

    return path.native_file_string();
}

//Default constructor
Blender::Blender(std::string mosaic_name, std::string file_type,
        unsigned int cache_size, int tile_size,
        bool draft, bool qtree, bool verbose){

    //Set debug level of the Blender and underlying NASA library tools
    if (verbose)
        set_debug_level(VerboseDebugMessage);

    //Set the cache size (cache_size MB)
    vw_system_cache().resize( cache_size*1024*1024 );

    //Tile size of images forming the pyramid
    this->tile_size = tile_size;

    //Wether to generate images into a pyramid
    this->qtree = qtree;

    //Wether to do blending or just position the images
    this->draft = draft;

    this->mosaic_name = mosaic_name;
    this->file_type = file_type;

}

/*Blending function
  Can work with different types of image encoding, recommended pixel type for colour images - RGBA<float>.
  Float is needed for blending and A gives alpha channel.
*/
template <class PixelT>
void Blender::blend(std::vector<boost::shared_ptr<InputImage> > images){
    mosaic::ImageComposite<PixelT> composite;
    //Don't do blending if draft mode, just compose the images together
    if( draft ) composite.set_draft_mode( true );

    //Iterator over the list of given transformed images
    std::vector<boost::shared_ptr<InputImage> >::iterator ifi;
    //Offset in the final mosaic
    cv::Point2d offset;
    int x, y;
    for (ifi = images.begin(); i != images.end(); i++){
        offset = ifi->getTransformedVersionOffset;
        x = offset.x;
        y = offset.y;
        std::cout << "Importing image file " << ifi->fileName
            << " at offet (" << x << "," << y << ")" << std::endl;
        composite.insert( DiskImageView<PixelT>( ifi->filename, false ), x, y );
    }

    vw_out(InfoMessage) << "Preparing the composite..." << std::endl;
    composite.prepare();
    vw_out(InfoMessage) << "Image dimensions: " << composite.cols() << " X " << composite.rows()  << std::endl;
    if( qtree ) {
        vw_out(InfoMessage) << "Preparing the quadtree..." << std::endl;
        mosaic::QuadTreeGenerator quadtree( composite, mosaic_name );
        quadtree.set_tile_size( tile_size );
        quadtree.set_file_type( file_type );
        quadtree.set_image_path_func( dz_formatted_image_path() );
        vw_out(InfoMessage) << "Generating..." << std::endl;
        quadtree.generate();
        vw_out(InfoMessage) << "Done!" << std::endl;
    }
    else {
        vw_out(InfoMessage) << "Blending..." << std::endl;
        write_image( mosaic_name+".blend."+file_type, composite );
        vw_out(InfoMessage) << "Done!" << std::endl;
    }

}

